//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.DirectoryServices.Protocols;
using System.Configuration;
using System.Globalization;
using System.DirectoryServices;
using Shp.Runtime.Contract;
using Shp.Runtime.Services.Properties;
using System.Security.Permissions;

namespace Shp.Runtime.Services
{
    static public class AuthenticationLogic
    {
        private const long AdsOptionPasswordMethod = 7;
        private const long AdsOptionPasswordPortNumber = 6;
        private const int AdsPasswordEncodeClear = 1;
        private const int AdsPasswordEncodeRequireSsl = 0;

        #region Users
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool CreateUser(Upn upn, string password, string email)
        {
            if (IsUser(upn))
            {
                return false;
            }

            else
            {
                string cs = AuthenticationHelper.GetConnectionString("OU=Users,OU={0}", upn.Domain);

                using (DirectoryEntry entry = new DirectoryEntry(cs))
                {
                    if (entry == null)
                        return false;

                    using (DirectoryEntry cnUserDirEntry = entry.Children.Add("CN=" + upn.UserName, "user"))
                    {
                        cnUserDirEntry.CommitChanges();
                        int port = int.Parse(AuthenticationHelper.Port, CultureInfo.InvariantCulture);
                        cnUserDirEntry.Invoke("SetOption", new object[] { AdsOptionPasswordPortNumber, port });
                        cnUserDirEntry.Invoke("SetOption", new object[] { AdsOptionPasswordMethod, AdsPasswordEncodeClear });
                        cnUserDirEntry.Invoke("SetPassword", new object[] { password });
                        cnUserDirEntry.Properties["userPrincipalName"].Value = upn.ToString();
                        cnUserDirEntry.Properties["mail"].Value = email;
                        cnUserDirEntry.Properties["msDS-UserAccountDisabled"].Value = "FALSE";
                        cnUserDirEntry.CommitChanges();
                        return true;
                    }
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool ValidateUser(Upn upn, string password)
        {
            string cs = AuthenticationHelper.GetConnectionString("OU=Users,OU={0}", upn.Domain);

            using (LdapConnection ldap = AuthenticationHelper.GetLdapConnection(cs))
            {
                NetworkCredential credentials = new NetworkCredential(upn.ToString(), password);

                try
                {
                    ldap.Bind(credentials);
                    return true;
                }

                catch (LdapException ex)
                {
                    if (ex.ErrorCode != 0x31)
                    {
                        throw;
                    }

                    return false;
                }
            }
        }

        static public PasswordPolicy GetPasswordPolicy()
        {
            PasswordPolicy policy = new PasswordPolicy();
            policy.MinRequiredPasswordLength = 8;
            policy.MinRequiredNonAlphanumericCharacters = 1;
            if (policy.MinRequiredNonAlphanumericCharacters > policy.MinRequiredPasswordLength)
            {
                throw new ArgumentException(Resources.InvalidPolicyExecption);
            }
            return policy;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool IsUser(Upn upn)
        {
            string filter = String.Format(CultureInfo.CurrentCulture, Constants.Ldap.UserFilter, upn.ToString());
            string cs = AuthenticationHelper.GetConnectionString("OU=Users,OU={0}", upn.Domain);

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                using (DirectorySearcher searcher = new DirectorySearcher(entry, filter, Constants.Ldap.UserProperties, System.DirectoryServices.SearchScope.Subtree))
                {
                    SearchResult result = searcher.FindOne();

                    if (result != null)
                    {
                        return true;
                    }

                    else
                    {
                        return false;
                    }
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public string[] GetUsers(string tenantAlias)
        {
            string cs = AuthenticationHelper.GetConnectionString("OU=Users,OU={0}", tenantAlias);

            List<string> list = new List<string>();

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                foreach (DirectoryEntry child in entry.Children)
                {
                    list.Add(child.Name.Substring(3));
                }
            }

            return list.ToArray();
        }
        #endregion

        #region Roles
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public void AddUserToRole(Upn upn, string roleName)
        {
            string cs = AuthenticationHelper.GetConnectionString("CN=Roles,OU={0}", upn.Domain);

            using (DirectoryEntry root = new DirectoryEntry(cs))
            {
                DirectoryEntry role = root.Children.Find("CN=" + roleName, "group");

                if (role != null)
                {
                    // Build a distinguisedName like this: CN=BetaTester,OU=Users,OU=Tests,O=Extranet
                    string dn = String.Format(CultureInfo.InvariantCulture, "CN={0},OU=Users,OU={1},O={2}", upn.UserName, upn.Domain, AuthenticationHelper.InstanceName);
                    role.Properties["member"].Add(dn);
                    role.CommitChanges();
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public void CreateRole(string tenantAlias, string roleName)
        {
            string cs = AuthenticationHelper.GetConnectionString("CN=Roles,OU={0}", tenantAlias);

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                using (DirectoryEntry newEntry = entry.Children.Add("CN=" + roleName, "group"))
                {
                    newEntry.CommitChanges();
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool DeleteRole(string tenantAlias, string roleName)
        {
            bool flag = false;

            string cs = AuthenticationHelper.GetConnectionString("CN=Roles,OU={0}", tenantAlias);

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                DirectoryEntry entryToDelete = entry.Children.Find("CN=" + roleName, "group");

                if (entryToDelete != null)
                {
                    entry.Children.Remove(entryToDelete);
                    flag = true;
                }
            }

            return flag;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool IsUserInRole(Upn upn, string roleName)
        {
            bool flag = false;

            string[] roles = GetRolesForUser(upn);
            foreach (string role in roles)
            {
                if (String.Compare(role, roleName, true, CultureInfo.InvariantCulture) == 0)
                {
                    flag = true;
                    break;
                }
            }

            return flag;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public bool RoleExists(string tenantAlias, string roleName)
        {
            bool flag = false;

            string[] roles = GetRoles(tenantAlias);
            foreach (string role in roles)
            {
                if (String.Compare(role, roleName, true, CultureInfo.InvariantCulture) == 0)
                {
                    flag = true;
                    break;
                }
            }

            return flag;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public void RemoveUserFromRole(Upn upn, string roleName)
        {
            string cs = AuthenticationHelper.GetConnectionString("CN=Roles,OU={0}", upn.Domain);

            using (DirectoryEntry root = new DirectoryEntry(cs))
            {
                DirectoryEntry role = root.Children.Find("CN=" + roleName, "group");

                if (role != null)
                {
                    // Build a distinguisedName like this: CN=BetaTester,OU=Users,OU=Tests,O=Extranet
                    string dn = String.Format(CultureInfo.InvariantCulture, "CN={0},OU=Users,OU={1},O={2}", upn.UserName, upn.Domain, AuthenticationHelper.InstanceName);

                    role.Properties["member"].Remove(dn);
                    role.CommitChanges();
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public string[] GetRoles(string tenantAlias)
        {
            string cs = AuthenticationHelper.GetConnectionString("CN=Roles,OU={0}", tenantAlias);

            List<string> list = new List<string>();

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                foreach (DirectoryEntry child in entry.Children)
                {
                    list.Add(child.Name.Substring(3));
                }
            }

            return list.ToArray();
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public string[] GetRolesForUser(Upn upn)
        {
            string cs = AuthenticationHelper.GetConnectionString("OU=Users,OU={0}", upn.Domain);
            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                using (DirectorySearcher searcher = new DirectorySearcher(entry))
                {
                    string[] roles = new string[0];
                    searcher.PropertiesToLoad.Add("memberOf");
                    searcher.PropertiesToLoad.Add("userPrincipalName");
                    searcher.Filter = String.Format(CultureInfo.InvariantCulture, Constants.Ldap.UserFilter, upn.ToString());
                    SearchResult result = searcher.FindOne();
                    DirectoryEntry userEntry = result.GetDirectoryEntry();
                    PropertyValueCollection property = userEntry.Properties["memberOf"];
                    if (property.Value is Array)
                    {
                        Array values = (Array)property.Value;
                        roles = new string[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            roles[i] = GetRoleName((string)values.GetValue(i));
                        }
                    }
                    else if (property.Value is string)
                    {
                        roles = new string[1];
                        roles[0] = GetRoleName((string)property.Value);
                    }
                    return roles;
                }
            }
        }

        private static string GetRoleName(string cn)
        {
            return cn.Split(',')[0].Split('=')[1];
        }
        #endregion

        #region Provisioning
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public void SetUpTenant(string tenantAlias)
        {
            string cs = AuthenticationHelper.GetConnectionString();

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                using (DirectoryEntry tenantEntry = entry.Children.Add("OU=" + tenantAlias, "OrganizationalUnit"))
                {
                    tenantEntry.CommitChanges();
                    using (DirectoryEntry usersEntry = tenantEntry.Children.Add("OU=Users", "OrganizationalUnit"))
                    {
                        usersEntry.CommitChanges();
                    }
                    using (DirectoryEntry rolesEntry = tenantEntry.Children.Add("CN=Roles", "Container"))
                    {
                        rolesEntry.CommitChanges();
                    }
                }
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        static public void CleanUpTenant(string tenantAlias)
        {
            string cs = AuthenticationHelper.GetConnectionString();

            using (DirectoryEntry entry = new DirectoryEntry(cs))
            {
                DirectoryEntry tenantToDelete = entry.Children.Find("OU=" + tenantAlias);

                if (tenantToDelete != null)
                {
                    tenantToDelete.DeleteTree();
                }
            }
        }
        #endregion
    }
}
